module i2c_driver(
    input             rst_n,
    input             clk_50M,
    input             i2c_valid,
    input  [1:0]      i2c_ctrl, 
    input             i2c_sclk,
    input             transfer_en,
    input             capture_en,
    // 00: start
    // 01: stop
    // 10: write
    // 11: read
    input  [7:0]      i2c_d_in,

    output [7:0]      i2c_d_out,
    output            i2c_done,
    output reg        ack_r,
    // i2c port
    output            scl,
    inout             sda
);


reg transfer_en_d1, transfer_en_d2;
always @(posedge clk_50M) begin
  transfer_en_d1 <= transfer_en;
  transfer_en_d2 <= transfer_en_d1;
end

// ---------------------state machine------------------
reg [2:0]   state,next_state;
reg [4:0]   wr_bit_cnt, rd_bit_cnt;

parameter   idle   = 3'd0; 
parameter   start  = 3'd1;
parameter   stop   = 3'd2;
parameter   write  = 3'd3;
parameter   wack   = 3'd4;
parameter   read   = 3'd5;
parameter   rack   = 3'd6;

always @ (posedge clk_50M) begin
  if (!rst_n) 
    state <= idle;
  else 
    state <= next_state;
end
always @ (*) begin
  if (transfer_en_d1)
  case (state)
    idle:     if (i2c_valid) begin
                case(i2c_ctrl)
                  2'b00:    next_state = start;
                  2'b01:    next_state = stop;
                  2'b10:    next_state = write;
                  2'b11:    next_state = read;
                  default:  next_state = idle;
                endcase
              end else 
                next_state = idle;
    start:    next_state = idle;
    stop:     next_state = idle;
    write:    if (wr_bit_cnt > 5'd7) 
                next_state = wack;
              else
                next_state = write;
    wack:     if (ack_r == 1'b0)
                next_state = idle;
              else 
                next_state = wack;
    read:     if (rd_bit_cnt > 5'd7)
                next_state = rack;
              else 
                next_state = read;
    rack:     if (ack_r == 1'b1)
                next_state = idle;
              else 
                next_state = rack;
    default:  next_state = idle;
  endcase
  else next_state = next_state;
end
// -----------------------------------------------------------

// ---------------------scl enable------------------------------
reg scl_en;
always @(posedge clk_50M) begin
  if ((!rst_n) || (state == idle))
    scl_en <= 0;
  else scl_en <= 1;
end
// -------------------------------------------------------------

// -------------------------sda-------------------------------
reg         sda_r;
reg         sda_link; // inout direction ctrl: 1-out, 0-in
// sda_link should be set to 0 before read
assign sda = (sda_link) ? sda_r : 1'bz;
assign scl = scl_en && i2c_sclk;

wire        cur_data_bit;
always @(posedge clk_50M) begin
  if (!rst_n) begin
    sda_link <= 1;
    sda_r <= 0;
  end else 
  case (state)
    start:    if (transfer_en_d2) begin sda_link <= 1; sda_r <= 1; end
              else if (capture_en)  begin sda_link <= 1; sda_r <= 0; end
                         else  begin sda_link <= sda_link; sda_r <= sda_r; end
    stop:     if (transfer_en_d2) begin sda_link <= 1; sda_r <= 0; end
              else if (capture_en)  begin sda_link <= 1; sda_r <= 1; end 
                         else  begin sda_link <= sda_link; sda_r <= sda_r; end
    write:    if (transfer_en_d2) begin sda_link <= 1; sda_r <= cur_data_bit; end
                         else  begin sda_link <= sda_link; sda_r <= sda_r; end

    wack:     if (transfer_en_d2) begin sda_link <= 0; sda_r <= sda_r; end
                         else  begin sda_link <= sda_link; sda_r <= sda_r; end
    read:     if (transfer_en_d2) begin sda_link <= 0; sda_r <= sda_r; end
                         else  begin sda_link <= sda_link; sda_r <= sda_r; end
    rack:     if (transfer_en_d2) begin sda_link <= 1; sda_r <= 1; end
                         else  begin sda_link <= sda_link; sda_r <= sda_r; end
    default:  begin sda_link <= sda_link; sda_r <= sda_r; end
  endcase
end
// ------------------------------------------------------------

// -----------------------write data reg-----------------------
reg [7:0]   wr_data_r;
always @(posedge clk_50M) begin 
  if (!rst_n)
    wr_data_r <= 8'd0;
  else if ((i2c_valid) && (i2c_ctrl == 2'b10))
    wr_data_r <= i2c_d_in;
  else if (capture_en) wr_data_r <= {wr_data_r[6:0], 1'b0};
  else wr_data_r <= wr_data_r;
end
assign cur_data_bit = wr_data_r[7];
// ------------------------------------------------------------

// -----------------------write_bit_count----------------------
always @(posedge clk_50M) begin
  if ((!rst_n) || (state == idle)) begin
    wr_bit_cnt <= 0;
  end else if ((capture_en) && (state == write))
    wr_bit_cnt <= wr_bit_cnt + 1;
  else wr_bit_cnt <= wr_bit_cnt;
end
// -------------------------------------------------------------

// ---------------------read bit reg----------------------------
reg [7:0]   rd_data_r;
always @(posedge clk_50M) begin
  if ((!rst_n) || ((state == idle) && (i2c_valid)))
    rd_data_r <= 8'd0;
  else if ((capture_en) && (state == read))
    rd_data_r <= {rd_data_r[6:0], sda};
  else 
    rd_data_r <= rd_data_r;
end
// -------------------------------------------------------------

// ---------------------i2c data out----------------------------
assign i2c_d_out = (state == idle) ? rd_data_r : 8'dx;
// -------------------------------------------------------------

// ---------------------read_bit_count--------------------------
always @(posedge clk_50M) begin
  if ((!rst_n) || (state == idle))
    rd_bit_cnt <= 0;
  else if ((capture_en) && (state == read))
    rd_bit_cnt <= rd_bit_cnt + 1;
  else rd_bit_cnt <= rd_bit_cnt;
end
// -------------------------------------------------------------

// ----------------------ack reg--------------------------------
always @(posedge clk_50M) begin
  if ((!rst_n) || (state == idle))
    ack_r <= 1'b1;
  else if (capture_en) begin
  if (state == write)
    ack_r <= 1'b1;
  else if (state == read)
    ack_r <= 1'b0;
  else if ((state == wack) || (state == rack))
    ack_r <= sda;
  else 
    ack_r <= ack_r;
  end else 
    ack_r <= ack_r;
end
// -------------------------------------------------------------

// ---------------------i2c done---------------------------------
assign i2c_done = (next_state == idle) && (!i2c_valid);
// -------------------------------------------------------------

endmodule